/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "effects/GrPorterDuffXferProcessor.h"

#include "GrBlend.h"
#include "GrDrawTargetCaps.h"
#include "GrProcessor.h"
#include "GrProcOptInfo.h"
#include "GrTypes.h"
#include "GrXferProcessor.h"
#include "gl/GrGLXferProcessor.h"
#include "gl/builders/GrGLFragmentShaderBuilder.h"
#include "gl/builders/GrGLProgramBuilder.h"

static bool can_tweak_alpha_for_coverage(GrBlendCoeff dstCoeff) {
    /*
     The fractional coverage is f.
     The src and dst coeffs are Cs and Cd.
     The dst and src colors are S and D.
     We want the blend to compute: f*Cs*S + (f*Cd + (1-f))D. By tweaking the source color's alpha
     we're replacing S with S'=fS. It's obvious that that first term will always be ok. The second
     term can be rearranged as [1-(1-Cd)f]D. By substituting in the various possibilities for Cd we
     find that only 1, ISA, and ISC produce the correct destination when applied to S' and D.
     */
    return kOne_GrBlendCoeff == dstCoeff ||
           kISA_GrBlendCoeff == dstCoeff ||
           kISC_GrBlendCoeff == dstCoeff;
}

class GrGLPorterDuffXferProcessor : public GrGLXferProcessor {
public:
    GrGLPorterDuffXferProcessor(const GrProcessor&) {}

    virtual ~GrGLPorterDuffXferProcessor() {}

    virtual void emitCode(const EmitArgs& args) SK_OVERRIDE {
        const GrPorterDuffXferProcessor& xp = args.fXP.cast<GrPorterDuffXferProcessor>();
        GrGLFPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder();
        if (xp.hasSecondaryOutput()) {
            switch(xp.secondaryOutputType()) {
                case GrPorterDuffXferProcessor::kCoverage_SecondaryOutputType:
                    fsBuilder->codeAppendf("%s = %s;", args.fOutputSecondary, args.fInputCoverage);
                    break;
                case GrPorterDuffXferProcessor::kCoverageISA_SecondaryOutputType:
                    fsBuilder->codeAppendf("%s = (1.0 - %s.a) * %s;",
                                           args.fOutputSecondary, args.fInputColor,
                                           args.fInputCoverage);
                    break;
                case GrPorterDuffXferProcessor::kCoverageISC_SecondaryOutputType:
                    fsBuilder->codeAppendf("%s = (vec4(1.0) - %s) * %s;",
                                           args.fOutputSecondary, args.fInputColor,
                                           args.fInputCoverage);
                    break;
                default:
                    SkFAIL("Unexpected Secondary Output");
            }
        }
        
        switch (xp.primaryOutputType()) {
            case GrPorterDuffXferProcessor::kNone_PrimaryOutputType:
                fsBuilder->codeAppendf("%s = vec4(0);", args.fOutputPrimary);
                break;
            case GrPorterDuffXferProcessor::kColor_PrimaryOutputType:
                fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputColor);
                break;
            case GrPorterDuffXferProcessor::kCoverage_PrimaryOutputType:
                fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputCoverage);
                break;
            case GrPorterDuffXferProcessor::kModulate_PrimaryOutputType:
            case GrPorterDuffXferProcessor::kCombineWithDst_PrimaryOutputType:
                fsBuilder->codeAppendf("%s = %s * %s;", args.fOutputPrimary, args.fInputColor,
                                       args.fInputCoverage);
                if (GrPorterDuffXferProcessor::kCombineWithDst_PrimaryOutputType ==
                    xp.primaryOutputType()){
                    fsBuilder->codeAppendf("%s += (vec4(1.0) - %s) * %s;", args.fOutputPrimary,
                                           args.fInputCoverage, fsBuilder->dstColor());
                }
                break;
            default:
                SkFAIL("Unexpected Primary Output");
        }
    }

    virtual void setData(const GrGLProgramDataManager&, const GrXferProcessor&) SK_OVERRIDE {};

    static void GenKey(const GrProcessor& processor, const GrGLCaps& caps,
                       GrProcessorKeyBuilder* b) {
        const GrPorterDuffXferProcessor& xp = processor.cast<GrPorterDuffXferProcessor>();
        b->add32(xp.primaryOutputType());
        b->add32(xp.secondaryOutputType());
    };

private:
    typedef GrGLXferProcessor INHERITED;
};

///////////////////////////////////////////////////////////////////////////////

GrPorterDuffXferProcessor::GrPorterDuffXferProcessor(GrBlendCoeff srcBlend, GrBlendCoeff dstBlend,
                                                     GrColor constant)
    : fSrcBlend(srcBlend)
    , fDstBlend(dstBlend)
    , fBlendConstant(constant)
    , fPrimaryOutputType(kModulate_PrimaryOutputType) 
    , fSecondaryOutputType(kNone_SecondaryOutputType) {
    this->initClassID<GrPorterDuffXferProcessor>();
}

GrPorterDuffXferProcessor::~GrPorterDuffXferProcessor() {
}

void GrPorterDuffXferProcessor::getGLProcessorKey(const GrGLCaps& caps,
                                                  GrProcessorKeyBuilder* b) const {
    GrGLPorterDuffXferProcessor::GenKey(*this, caps, b);
}

GrGLXferProcessor* GrPorterDuffXferProcessor::createGLInstance() const {
    return SkNEW_ARGS(GrGLPorterDuffXferProcessor, (*this));
}

GrXferProcessor::OptFlags
GrPorterDuffXferProcessor::getOptimizations(const GrProcOptInfo& colorPOI,
                                            const GrProcOptInfo& coveragePOI,
                                            bool doesStencilWrite,
                                            GrColor* overrideColor,
                                            const GrDrawTargetCaps& caps) {
    GrXferProcessor::OptFlags optFlags;
    // Optimizations when doing RGB Coverage
    if (coveragePOI.isFourChannelOutput()) {
        // We want to force our primary output to be alpha * Coverage, where alpha is the alpha
        // value of the blend the constant. We should already have valid blend coeff's if we are at
        // a point where we have RGB coverage. We don't need any color stages since the known color
        // output is already baked into the blendConstant.
        uint8_t alpha = GrColorUnpackA(fBlendConstant);
        *overrideColor = GrColorPackRGBA(alpha, alpha, alpha, alpha);
        optFlags = GrXferProcessor::kOverrideColor_OptFlag;
    } else {
        optFlags = this->internalGetOptimizations(colorPOI,
                                                  coveragePOI,
                                                  doesStencilWrite);
    }
    this->calcOutputTypes(optFlags, caps, coveragePOI.isSolidWhite(),
                          colorPOI.readsDst() || coveragePOI.readsDst());
    return optFlags;
}

void GrPorterDuffXferProcessor::calcOutputTypes(GrXferProcessor::OptFlags optFlags,
                                                const GrDrawTargetCaps& caps,
                                                bool hasSolidCoverage, bool readsDst) {
    if (optFlags & kIgnoreColor_OptFlag) {
        if (optFlags & kIgnoreCoverage_OptFlag) {
            fPrimaryOutputType = kNone_PrimaryOutputType;
            return;
        } else {
            fPrimaryOutputType = kCoverage_PrimaryOutputType;
            return;
        }
    } else if (optFlags & kIgnoreCoverage_OptFlag) {
        fPrimaryOutputType = kColor_PrimaryOutputType;
        return;
    }

    // If we do have coverage determine whether it matters.  Dual source blending is expensive so
    // we don't do it if we are doing coverage drawing.  If we aren't then We always do dual source
    // blending if we have any effective coverage stages OR the geometry processor doesn't emits
    // solid coverage.
    if (!(optFlags & kSetCoverageDrawing_OptFlag) && !hasSolidCoverage) {
        if (caps.dualSourceBlendingSupport()) {
            if (kZero_GrBlendCoeff == fDstBlend) {
                // write the coverage value to second color
                fSecondaryOutputType = kCoverage_SecondaryOutputType;
                fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
            } else if (kSA_GrBlendCoeff == fDstBlend) {
                // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
                fSecondaryOutputType = kCoverageISA_SecondaryOutputType;
                fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
            } else if (kSC_GrBlendCoeff == fDstBlend) {
                // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered.
                fSecondaryOutputType = kCoverageISC_SecondaryOutputType;
                fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff;
            }
        } else if (readsDst &&
                   kOne_GrBlendCoeff == fSrcBlend &&
                   kZero_GrBlendCoeff == fDstBlend) {
            fPrimaryOutputType = kCombineWithDst_PrimaryOutputType;
        }
    }
}

GrXferProcessor::OptFlags
GrPorterDuffXferProcessor::internalGetOptimizations(const GrProcOptInfo& colorPOI,
                                                    const GrProcOptInfo& coveragePOI,
                                                    bool doesStencilWrite) {
    bool srcAIsOne;
    bool hasCoverage;

    srcAIsOne = colorPOI.isOpaque();
    hasCoverage = !coveragePOI.isSolidWhite();

    bool dstCoeffIsOne = kOne_GrBlendCoeff == fDstBlend ||
                         (kSA_GrBlendCoeff == fDstBlend && srcAIsOne);
    bool dstCoeffIsZero = kZero_GrBlendCoeff == fDstBlend ||
                         (kISA_GrBlendCoeff == fDstBlend && srcAIsOne);

    // When coeffs are (0,1) there is no reason to draw at all, unless
    // stenciling is enabled. Having color writes disabled is effectively
    // (0,1).
    if ((kZero_GrBlendCoeff == fSrcBlend && dstCoeffIsOne)) {
        if (doesStencilWrite) {
            return GrXferProcessor::kIgnoreColor_OptFlag |
                   GrXferProcessor::kSetCoverageDrawing_OptFlag;
        } else {
            fDstBlend = kOne_GrBlendCoeff;
            return GrXferProcessor::kSkipDraw_OptFlag;
        }
    }

    // if we don't have coverage we can check whether the dst
    // has to read at all. If not, we'll disable blending.
    if (!hasCoverage) {
        if (dstCoeffIsZero) {
            if (kOne_GrBlendCoeff == fSrcBlend) {
                // if there is no coverage and coeffs are (1,0) then we
                // won't need to read the dst at all, it gets replaced by src
                fDstBlend = kZero_GrBlendCoeff;
                return GrXferProcessor::kNone_Opt;
            } else if (kZero_GrBlendCoeff == fSrcBlend) {
                // if the op is "clear" then we don't need to emit a color
                // or blend, just write transparent black into the dst.
                fSrcBlend = kOne_GrBlendCoeff;
                fDstBlend = kZero_GrBlendCoeff;
                return GrXferProcessor::kIgnoreColor_OptFlag |
                       GrXferProcessor::kIgnoreCoverage_OptFlag;
            }
        }
    }  else {
        // check whether coverage can be safely rolled into alpha
        // of if we can skip color computation and just emit coverage
        if (can_tweak_alpha_for_coverage(fDstBlend)) {
            return GrXferProcessor::kSetCoverageDrawing_OptFlag;
        }
        if (dstCoeffIsZero) {
            if (kZero_GrBlendCoeff == fSrcBlend) {
                // the source color is not included in the blend
                // the dst coeff is effectively zero so blend works out to:
                // (c)(0)D + (1-c)D = (1-c)D.
                fDstBlend = kISA_GrBlendCoeff;
                return GrXferProcessor::kIgnoreColor_OptFlag |
                       GrXferProcessor::kSetCoverageDrawing_OptFlag;
            } else if (srcAIsOne) {
                // the dst coeff is effectively zero so blend works out to:
                // cS + (c)(0)D + (1-c)D = cS + (1-c)D.
                // If Sa is 1 then we can replace Sa with c
                // and set dst coeff to 1-Sa.
                fDstBlend = kISA_GrBlendCoeff;
                return GrXferProcessor::kSetCoverageDrawing_OptFlag;
            }
        } else if (dstCoeffIsOne) {
            // the dst coeff is effectively one so blend works out to:
            // cS + (c)(1)D + (1-c)D = cS + D.
            fDstBlend = kOne_GrBlendCoeff;
            return GrXferProcessor::kSetCoverageDrawing_OptFlag;
        }
    }

    return GrXferProcessor::kNone_Opt;
}

bool GrPorterDuffXferProcessor::hasSecondaryOutput() const {
    return kNone_SecondaryOutputType != fSecondaryOutputType;
}

///////////////////////////////////////////////////////////////////////////////

GrPorterDuffXPFactory::GrPorterDuffXPFactory(GrBlendCoeff src, GrBlendCoeff dst)
    : fSrcCoeff(src), fDstCoeff(dst) {
    this->initClassID<GrPorterDuffXPFactory>();
}

GrXPFactory* GrPorterDuffXPFactory::Create(SkXfermode::Mode mode) {
    switch (mode) {
        case SkXfermode::kClear_Mode: {
            static GrPorterDuffXPFactory gClearPDXPF(kZero_GrBlendCoeff, kZero_GrBlendCoeff);
            return SkRef(&gClearPDXPF);
            break;
        }
        case SkXfermode::kSrc_Mode: {
            static GrPorterDuffXPFactory gSrcPDXPF(kOne_GrBlendCoeff, kZero_GrBlendCoeff);
            return SkRef(&gSrcPDXPF);
            break;
        }
        case SkXfermode::kDst_Mode: {
            static GrPorterDuffXPFactory gDstPDXPF(kZero_GrBlendCoeff, kOne_GrBlendCoeff);
            return SkRef(&gDstPDXPF);
            break;
        }
        case SkXfermode::kSrcOver_Mode: {
            static GrPorterDuffXPFactory gSrcOverPDXPF(kOne_GrBlendCoeff, kISA_GrBlendCoeff);
            return SkRef(&gSrcOverPDXPF);
            break;
        }
        case SkXfermode::kDstOver_Mode: {
            static GrPorterDuffXPFactory gDstOverPDXPF(kIDA_GrBlendCoeff, kOne_GrBlendCoeff);
            return SkRef(&gDstOverPDXPF);
            break;
        }
        case SkXfermode::kSrcIn_Mode: {
            static GrPorterDuffXPFactory gSrcInPDXPF(kDA_GrBlendCoeff, kZero_GrBlendCoeff);
            return SkRef(&gSrcInPDXPF);
            break;
        }
        case SkXfermode::kDstIn_Mode: {
            static GrPorterDuffXPFactory gDstInPDXPF(kZero_GrBlendCoeff, kSA_GrBlendCoeff);
            return SkRef(&gDstInPDXPF);
            break;
        }
        case SkXfermode::kSrcOut_Mode: {
            static GrPorterDuffXPFactory gSrcOutPDXPF(kIDA_GrBlendCoeff, kZero_GrBlendCoeff);
            return SkRef(&gSrcOutPDXPF);
            break;
        }
        case SkXfermode::kDstOut_Mode: {
            static GrPorterDuffXPFactory gDstOutPDXPF(kZero_GrBlendCoeff, kISA_GrBlendCoeff);
            return SkRef(&gDstOutPDXPF);
            break;
        }
        case SkXfermode::kSrcATop_Mode: {
            static GrPorterDuffXPFactory gSrcATopPDXPF(kDA_GrBlendCoeff, kISA_GrBlendCoeff);
            return SkRef(&gSrcATopPDXPF);
            break;
        }
        case SkXfermode::kDstATop_Mode: {
            static GrPorterDuffXPFactory gDstATopPDXPF(kIDA_GrBlendCoeff, kSA_GrBlendCoeff);
            return SkRef(&gDstATopPDXPF);
            break;
        }
        case SkXfermode::kXor_Mode: {
            static GrPorterDuffXPFactory gXorPDXPF(kIDA_GrBlendCoeff, kISA_GrBlendCoeff);
            return SkRef(&gXorPDXPF);
            break;
        }
        case SkXfermode::kPlus_Mode: {
            static GrPorterDuffXPFactory gPlusPDXPF(kOne_GrBlendCoeff, kOne_GrBlendCoeff);
            return SkRef(&gPlusPDXPF);
            break;
        }
        case SkXfermode::kModulate_Mode: {
            static GrPorterDuffXPFactory gModulatePDXPF(kZero_GrBlendCoeff, kSC_GrBlendCoeff);
            return SkRef(&gModulatePDXPF);
            break;
        }
        case SkXfermode::kScreen_Mode: {
            static GrPorterDuffXPFactory gScreenPDXPF(kOne_GrBlendCoeff, kISC_GrBlendCoeff);
            return SkRef(&gScreenPDXPF);
            break;
        }
        default:
            return NULL;
    }
}

GrXferProcessor* GrPorterDuffXPFactory::createXferProcessor(const GrProcOptInfo& colorPOI,
                                                            const GrProcOptInfo& covPOI) const {
    if (!covPOI.isFourChannelOutput()) {
        return GrPorterDuffXferProcessor::Create(fSrcCoeff, fDstCoeff);
    } else {
        if (this->supportsRGBCoverage(colorPOI.color(), colorPOI.validFlags())) {
            SkASSERT(kRGBA_GrColorComponentFlags == colorPOI.validFlags());
            GrColor blendConstant = GrUnPreMulColor(colorPOI.color());
            return GrPorterDuffXferProcessor::Create(kConstC_GrBlendCoeff, kISC_GrBlendCoeff,
                                                     blendConstant);
        } else {
            return NULL;
        }
    }
}

bool GrPorterDuffXPFactory::supportsRGBCoverage(GrColor /*knownColor*/,
                                                uint32_t knownColorFlags) const {
    if (kOne_GrBlendCoeff == fSrcCoeff && kISA_GrBlendCoeff == fDstCoeff &&
        kRGBA_GrColorComponentFlags == knownColorFlags) {
        return true;
    }
    return false;
}

bool GrPorterDuffXPFactory::canApplyCoverage(const GrProcOptInfo& colorPOI,
                                             const GrProcOptInfo& coveragePOI) const {
    bool srcAIsOne = colorPOI.isOpaque();

    bool dstCoeffIsOne = kOne_GrBlendCoeff == fDstCoeff ||
                         (kSA_GrBlendCoeff == fDstCoeff && srcAIsOne);
    bool dstCoeffIsZero = kZero_GrBlendCoeff == fDstCoeff ||
                         (kISA_GrBlendCoeff == fDstCoeff && srcAIsOne);

    if ((kZero_GrBlendCoeff == fSrcCoeff && dstCoeffIsOne)) {
        return true;
    }

    // if we don't have coverage we can check whether the dst
    // has to read at all.
    // check whether coverage can be safely rolled into alpha
    // of if we can skip color computation and just emit coverage
    if (this->canTweakAlphaForCoverage()) {
        return true;
    }
    if (dstCoeffIsZero) {
        if (kZero_GrBlendCoeff == fSrcCoeff) {
            return true;
        } else if (srcAIsOne) {
            return  true;
        }
    } else if (dstCoeffIsOne) {
        return true;
    }

    // TODO: once all SkXferEffects are XP's then we will never reads dst here since only XP's
    // will readDst and PD XP's don't read dst.
    if ((colorPOI.readsDst() || coveragePOI.readsDst()) &&
        kOne_GrBlendCoeff == fSrcCoeff && kZero_GrBlendCoeff == fDstCoeff) {
        return true;
    }

    return false;
}

bool GrPorterDuffXPFactory::canTweakAlphaForCoverage() const {
    return can_tweak_alpha_for_coverage(fDstCoeff);
}

void GrPorterDuffXPFactory::getInvariantOutput(const GrProcOptInfo& colorPOI,
                                               const GrProcOptInfo& coveragePOI,
                                               GrXPFactory::InvariantOutput* output) const {
    if (!coveragePOI.isSolidWhite()) {
        output->fWillBlendWithDst = true;
        output->fBlendedColorFlags = 0;
        return;
    }

    GrBlendCoeff srcCoeff = fSrcCoeff;
    GrBlendCoeff dstCoeff = fDstCoeff;

    // TODO: figure out to merge this simplify with other current optimization code paths and
    // eventually remove from GrBlend
    GrSimplifyBlend(&srcCoeff, &dstCoeff, colorPOI.color(), colorPOI.validFlags(),
                    0, 0, 0);

    if (GrBlendCoeffRefsDst(srcCoeff)) {
        output->fWillBlendWithDst = true;
        output->fBlendedColorFlags = 0;
        return;
    }

    if (kZero_GrBlendCoeff != dstCoeff) {
        bool srcAIsOne = colorPOI.isOpaque();
        if (kISA_GrBlendCoeff != dstCoeff || !srcAIsOne) {
            output->fWillBlendWithDst = true;
        }
        output->fBlendedColorFlags = 0;
        return;
    }

    switch (srcCoeff) {
        case kZero_GrBlendCoeff:
            output->fBlendedColor = 0;
            output->fBlendedColorFlags = kRGBA_GrColorComponentFlags;
            break;

        case kOne_GrBlendCoeff:
            output->fBlendedColor = colorPOI.color();
            output->fBlendedColorFlags = colorPOI.validFlags();
            break;

            // The src coeff should never refer to the src and if it refers to dst then opaque
            // should have been false.
        case kSC_GrBlendCoeff:
        case kISC_GrBlendCoeff:
        case kDC_GrBlendCoeff:
        case kIDC_GrBlendCoeff:
        case kSA_GrBlendCoeff:
        case kISA_GrBlendCoeff:
        case kDA_GrBlendCoeff:
        case kIDA_GrBlendCoeff:
        default:
            SkFAIL("srcCoeff should not refer to src or dst.");
            break;

            // TODO: update this once GrPaint actually has a const color.
        case kConstC_GrBlendCoeff:
        case kIConstC_GrBlendCoeff:
        case kConstA_GrBlendCoeff:
        case kIConstA_GrBlendCoeff:
            output->fBlendedColorFlags = 0;
            break;
    }

    // TODO: once all SkXferEffects are XP's then we will never reads dst here since only XP's
    // will readDst and PD XP's don't read dst.
    if (colorPOI.readsDst() || coveragePOI.readsDst()) {
        output->fWillBlendWithDst = true;
        return;
    }
    output->fWillBlendWithDst = false;
}

bool GrPorterDuffXPFactory::willReadDst(const GrProcOptInfo& colorPOI,
                                        const GrProcOptInfo& coveragePOI) const {
    return colorPOI.readsDst() || coveragePOI.readsDst();
}

GR_DEFINE_XP_FACTORY_TEST(GrPorterDuffXPFactory);

GrXPFactory* GrPorterDuffXPFactory::TestCreate(SkRandom* random,
                                               GrContext*,
                                               const GrDrawTargetCaps&,
                                               GrTexture*[]) {
    GrBlendCoeff src;
    do {
        src = GrBlendCoeff(random->nextRangeU(kFirstPublicGrBlendCoeff, kLastPublicGrBlendCoeff));
    } while (GrBlendCoeffRefsSrc(src));

    GrBlendCoeff dst;
    do {
        dst = GrBlendCoeff(random->nextRangeU(kFirstPublicGrBlendCoeff, kLastPublicGrBlendCoeff));
    } while (GrBlendCoeffRefsDst(dst));

    return GrPorterDuffXPFactory::Create(src, dst);
}

